// TODO：使用服务实现，绑定用户并设置失效时间
package token

import (
	"bytes"
	"crypto/hmac"
	"crypto/sha1"
	"fmt"
	"io"
	"strings"

	"golang.org/x/net/context"

	"xp/lib/mux"
	"xp/lib/utils"
)

func tokenFromCtx(ctx *mux.Ctx) string {
	authorization := ctx.GetHeader("Authorization")
	tokens := bytes.Split(authorization, []byte(" "))
	if len(tokens) != 2 || !bytes.EqualFold(tokens[0], []byte("Bearer")) {
		return ""
	}
	return string(tokens[1])
}

// Auther 作为一个验证当前请求是否授权的中间件
// 授权通过Authorization: Bearer token方式传递过来
func Auther(next mux.Handler) mux.Handler {
	return func(ctx *mux.Ctx) {
		ok, err := Validate(ctx, tokenFromCtx(ctx), CurrentUser(ctx))
		if err != nil {
			ctx.Error(err)
			return
		}
		if !ok {
			ctx.Status(401, []byte("user unauthenticated"))
			return
		}
		next(ctx)
	}
}

var (
	xCurrentUser = `X-ND-Current-User`
)

// 登录请求的时候设置当前用户请求头，方便后续逻辑处理
func Login(ctx *mux.Ctx, account string) {
	ctx.Request.Header.Set(xCurrentUser, account)
}

// CurrentUser 从HTTP头部中返回当前登录的用户，需要与客户端协商好传递的参数名称一致
func CurrentUser(ctx *mux.Ctx) string {
	return utils.Bytes2Str(ctx.GetHeader(xCurrentUser))
}

var (
	hmacKey = []byte(`2nDadminNDPToken3Security1nDadminNDPToken3Security3`)
)

// New 根据用户账户生成一个授权值，在无状态的HTTP请求下可以通过这个授权值验证一个用户
func New(ctx context.Context, account string) (string, error) {
	return genToken(utils.NewRandom(), account), nil
}

func genToken(salt, account string) string {
	w := hmac.New(sha1.New, hmacKey)
	io.WriteString(w, salt+account)
	return fmt.Sprintf("%s:%x", strings.Replace(salt, "-", "", -1), w.Sum(nil))
}

// Validate 通过token中的salt和当前登录的账户，重新生成一次token并检查是否相等
func Validate(ctx context.Context, token, account string) (bool, error) {
	tokens := strings.Split(token, ":")
	if len(tokens) != 2 {
		return false, nil
	}
	tk := tokens[0]
	if len(tk) < 20 {
		return false, nil
	}
	salt := tk[0:8] + "-" + tk[8:12] + "-" + tk[12:16] + "-" + tk[16:20] + "-" + tk[20:]
	return strings.EqualFold(genToken(salt, account), token), nil
}
